3 * Options for the PHP parser
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 use Wikimedia\ScopedCallback
;
26 * @brief Set options of the Parser
28 * How to add an option in core:
29 * 1. Add it to one of the arrays in ParserOptions::setDefaults()
30 * 2. If necessary, add an entry to ParserOptions::$inCacheKey
31 * 3. Add a getter and setter in the section for that.
33 * How to add an option in an extension:
34 * 1. Use the 'ParserOptionsRegister' hook to register it.
35 * 2. Where necessary, use $popt->getOption() and $popt->setOption()
43 * Default values for all options that are relevant for caching.
44 * @see self::getDefaults()
47 private static $defaults = null;
53 private static $lazyOptions = [
54 'dateformat' => [ __CLASS__
, 'initDateFormat' ],
58 * Specify options that are included in the cache key
61 private static $inCacheKey = [
63 'numberheadings' => true,
65 'stubthreshold' => true,
71 * Current values for all options that are relevant for caching.
77 * Timestamp used for {{CURRENTDAY}} etc.
79 * @note Caching based on parse time is handled externally
84 * The edit section flag is in ParserOptions for historical reasons, but
85 * doesn't actually affect the parser output since Feb 2015.
88 private $mEditSection = true;
93 * @todo Track this for caching somehow without fragmenting the cache insanely
98 * Function to be called when an option is accessed.
100 * @note Used for collecting used options, does not affect caching
102 private $onAccessCallback = null;
105 * If the page being parsed is a redirect, this should hold the redirect
108 * @todo Track this for caching somehow
110 private $redirectTarget = null;
113 * Appended to the options hash
115 private $mExtraKey = '';
118 * @name Option accessors
123 * Fetch an option, generically
125 * @param string $name Option name
128 public function getOption( $name ) {
129 if ( !array_key_exists( $name, $this->options
) ) {
130 throw new InvalidArgumentException( "Unknown parser option $name" );
133 if ( isset( self
::$lazyOptions[$name] ) && $this->options
[$name] === null ) {
134 $this->options
[$name] = call_user_func( self
::$lazyOptions[$name], $this, $name );
136 if ( !empty( self
::$inCacheKey[$name] ) ) {
137 $this->optionUsed( $name );
139 return $this->options
[$name];
143 * Set an option, generically
145 * @param string $name Option name
146 * @param mixed $value New value. Passing null will set null, unlike many
147 * of the existing accessors which ignore null for historical reasons.
148 * @return mixed Old value
150 public function setOption( $name, $value ) {
151 if ( !array_key_exists( $name, $this->options
) ) {
152 throw new InvalidArgumentException( "Unknown parser option $name" );
154 $old = $this->options
[$name];
155 $this->options
[$name] = $value;
160 * Legacy implementation
161 * @since 1.30 For implementing legacy setters only. Don't use this in new code.
162 * @deprecated since 1.30
163 * @param string $name Option name
164 * @param mixed $value New value. Passing null does not set the value.
165 * @return mixed Old value
167 protected function setOptionLegacy( $name, $value ) {
168 if ( !array_key_exists( $name, $this->options
) ) {
169 throw new InvalidArgumentException( "Unknown parser option $name" );
171 return wfSetVar( $this->options
[$name], $value );
175 * Whether to extract interlanguage links
177 * When true, interlanguage links will be returned by
178 * ParserOutput::getLanguageLinks() instead of generating link HTML.
182 public function getInterwikiMagic() {
183 return $this->getOption( 'interwikiMagic' );
187 * Specify whether to extract interlanguage links
188 * @param bool|null $x New value (null is no change)
189 * @return bool Old value
191 public function setInterwikiMagic( $x ) {
192 return $this->setOptionLegacy( 'interwikiMagic', $x );
196 * Allow all external images inline?
199 public function getAllowExternalImages() {
200 return $this->getOption( 'allowExternalImages' );
204 * Allow all external images inline?
205 * @param bool|null $x New value (null is no change)
206 * @return bool Old value
208 public function setAllowExternalImages( $x ) {
209 return $this->setOptionLegacy( 'allowExternalImages', $x );
213 * External images to allow
215 * When self::getAllowExternalImages() is false
217 * @return string|string[] URLs to allow
219 public function getAllowExternalImagesFrom() {
220 return $this->getOption( 'allowExternalImagesFrom' );
224 * External images to allow
226 * When self::getAllowExternalImages() is false
228 * @param string|string[]|null $x New value (null is no change)
229 * @return string|string[] Old value
231 public function setAllowExternalImagesFrom( $x ) {
232 return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
236 * Use the on-wiki external image whitelist?
239 public function getEnableImageWhitelist() {
240 return $this->getOption( 'enableImageWhitelist' );
244 * Use the on-wiki external image whitelist?
245 * @param bool|null $x New value (null is no change)
246 * @return bool Old value
248 public function setEnableImageWhitelist( $x ) {
249 return $this->setOptionLegacy( 'enableImageWhitelist', $x );
253 * Automatically number headings?
256 public function getNumberHeadings() {
257 return $this->getOption( 'numberheadings' );
261 * Automatically number headings?
262 * @param bool|null $x New value (null is no change)
263 * @return bool Old value
265 public function setNumberHeadings( $x ) {
266 return $this->setOptionLegacy( 'numberheadings', $x );
270 * Allow inclusion of special pages?
273 public function getAllowSpecialInclusion() {
274 return $this->getOption( 'allowSpecialInclusion' );
278 * Allow inclusion of special pages?
279 * @param bool|null $x New value (null is no change)
280 * @return bool Old value
282 public function setAllowSpecialInclusion( $x ) {
283 return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
287 * Use tidy to cleanup output HTML?
290 public function getTidy() {
291 return $this->getOption( 'tidy' );
295 * Use tidy to cleanup output HTML?
296 * @param bool|null $x New value (null is no change)
297 * @return bool Old value
299 public function setTidy( $x ) {
300 return $this->setOptionLegacy( 'tidy', $x );
304 * Parsing an interface message?
307 public function getInterfaceMessage() {
308 return $this->getOption( 'interfaceMessage' );
312 * Parsing an interface message?
313 * @param bool|null $x New value (null is no change)
314 * @return bool Old value
316 public function setInterfaceMessage( $x ) {
317 return $this->setOptionLegacy( 'interfaceMessage', $x );
321 * Target language for the parse
322 * @return Language|null
324 public function getTargetLanguage() {
325 return $this->getOption( 'targetLanguage' );
329 * Target language for the parse
330 * @param Language|null $x New value
331 * @return Language|null Old value
333 public function setTargetLanguage( $x ) {
334 return $this->setOption( 'targetLanguage', $x );
338 * Maximum size of template expansions, in bytes
341 public function getMaxIncludeSize() {
342 return $this->getOption( 'maxIncludeSize' );
346 * Maximum size of template expansions, in bytes
347 * @param int|null $x New value (null is no change)
348 * @return int Old value
350 public function setMaxIncludeSize( $x ) {
351 return $this->setOptionLegacy( 'maxIncludeSize', $x );
355 * Maximum number of nodes touched by PPFrame::expand()
358 public function getMaxPPNodeCount() {
359 return $this->getOption( 'maxPPNodeCount' );
363 * Maximum number of nodes touched by PPFrame::expand()
364 * @param int|null $x New value (null is no change)
365 * @return int Old value
367 public function setMaxPPNodeCount( $x ) {
368 return $this->setOptionLegacy( 'maxPPNodeCount', $x );
372 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
375 public function getMaxGeneratedPPNodeCount() {
376 return $this->getOption( 'maxGeneratedPPNodeCount' );
380 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
381 * @param int|null $x New value (null is no change)
384 public function setMaxGeneratedPPNodeCount( $x ) {
385 return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
389 * Maximum recursion depth in PPFrame::expand()
392 public function getMaxPPExpandDepth() {
393 return $this->getOption( 'maxPPExpandDepth' );
397 * Maximum recursion depth for templates within templates
400 public function getMaxTemplateDepth() {
401 return $this->getOption( 'maxTemplateDepth' );
405 * Maximum recursion depth for templates within templates
406 * @param int|null $x New value (null is no change)
407 * @return int Old value
409 public function setMaxTemplateDepth( $x ) {
410 return $this->setOptionLegacy( 'maxTemplateDepth', $x );
414 * Maximum number of calls per parse to expensive parser functions
418 public function getExpensiveParserFunctionLimit() {
419 return $this->getOption( 'expensiveParserFunctionLimit' );
423 * Maximum number of calls per parse to expensive parser functions
425 * @param int|null $x New value (null is no change)
426 * @return int Old value
428 public function setExpensiveParserFunctionLimit( $x ) {
429 return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
433 * Remove HTML comments
434 * @warning Only applies to preprocess operations
437 public function getRemoveComments() {
438 return $this->getOption( 'removeComments' );
442 * Remove HTML comments
443 * @warning Only applies to preprocess operations
444 * @param bool|null $x New value (null is no change)
445 * @return bool Old value
447 public function setRemoveComments( $x ) {
448 return $this->setOptionLegacy( 'removeComments', $x );
452 * Enable limit report in an HTML comment on output
455 public function getEnableLimitReport() {
456 return $this->getOption( 'enableLimitReport' );
460 * Enable limit report in an HTML comment on output
461 * @param bool|null $x New value (null is no change)
462 * @return bool Old value
464 public function enableLimitReport( $x = true ) {
465 return $this->setOptionLegacy( 'enableLimitReport', $x );
469 * Clean up signature texts?
470 * @see Parser::cleanSig
473 public function getCleanSignatures() {
474 return $this->getOption( 'cleanSignatures' );
478 * Clean up signature texts?
479 * @see Parser::cleanSig
480 * @param bool|null $x New value (null is no change)
481 * @return bool Old value
483 public function setCleanSignatures( $x ) {
484 return $this->setOptionLegacy( 'cleanSignatures', $x );
488 * Target attribute for external links
491 public function getExternalLinkTarget() {
492 return $this->getOption( 'externalLinkTarget' );
496 * Target attribute for external links
497 * @param string|null $x New value (null is no change)
498 * @return string Old value
500 public function setExternalLinkTarget( $x ) {
501 return $this->setOptionLegacy( 'externalLinkTarget', $x );
505 * Whether content conversion should be disabled
508 public function getDisableContentConversion() {
509 return $this->getOption( 'disableContentConversion' );
513 * Whether content conversion should be disabled
514 * @param bool|null $x New value (null is no change)
515 * @return bool Old value
517 public function disableContentConversion( $x = true ) {
518 return $this->setOptionLegacy( 'disableContentConversion', $x );
522 * Whether title conversion should be disabled
525 public function getDisableTitleConversion() {
526 return $this->getOption( 'disableTitleConversion' );
530 * Whether title conversion should be disabled
531 * @param bool|null $x New value (null is no change)
532 * @return bool Old value
534 public function disableTitleConversion( $x = true ) {
535 return $this->setOptionLegacy( 'disableTitleConversion', $x );
539 * Thumb size preferred by the user.
542 public function getThumbSize() {
543 return $this->getOption( 'thumbsize' );
547 * Thumb size preferred by the user.
548 * @param int|null $x New value (null is no change)
549 * @return int Old value
551 public function setThumbSize( $x ) {
552 return $this->setOptionLegacy( 'thumbsize', $x );
556 * Thumb size preferred by the user.
559 public function getStubThreshold() {
560 return $this->getOption( 'stubthreshold' );
564 * Thumb size preferred by the user.
565 * @param int|null $x New value (null is no change)
566 * @return int Old value
568 public function setStubThreshold( $x ) {
569 return $this->setOptionLegacy( 'stubthreshold', $x );
573 * Parsing the page for a "preview" operation?
576 public function getIsPreview() {
577 return $this->getOption( 'isPreview' );
581 * Parsing the page for a "preview" operation?
582 * @param bool|null $x New value (null is no change)
583 * @return bool Old value
585 public function setIsPreview( $x ) {
586 return $this->setOptionLegacy( 'isPreview', $x );
590 * Parsing the page for a "preview" operation on a single section?
593 public function getIsSectionPreview() {
594 return $this->getOption( 'isSectionPreview' );
598 * Parsing the page for a "preview" operation on a single section?
599 * @param bool|null $x New value (null is no change)
600 * @return bool Old value
602 public function setIsSectionPreview( $x ) {
603 return $this->setOptionLegacy( 'isSectionPreview', $x );
607 * Parsing the printable version of the page?
610 public function getIsPrintable() {
611 return $this->getOption( 'printable' );
615 * Parsing the printable version of the page?
616 * @param bool|null $x New value (null is no change)
617 * @return bool Old value
619 public function setIsPrintable( $x ) {
620 return $this->setOptionLegacy( 'printable', $x );
624 * Transform wiki markup when saving the page?
627 public function getPreSaveTransform() {
628 return $this->getOption( 'preSaveTransform' );
632 * Transform wiki markup when saving the page?
633 * @param bool|null $x New value (null is no change)
634 * @return bool Old value
636 public function setPreSaveTransform( $x ) {
637 return $this->setOptionLegacy( 'preSaveTransform', $x );
644 public function getDateFormat() {
645 return $this->getOption( 'dateformat' );
649 * Lazy initializer for dateFormat
651 private static function initDateFormat( $popt ) {
652 return $popt->mUser
->getDatePreference();
657 * @param string|null $x New value (null is no change)
658 * @return string Old value
660 public function setDateFormat( $x ) {
661 return $this->setOptionLegacy( 'dateformat', $x );
665 * Get the user language used by the parser for this page and split the parser cache.
667 * @warning: Calling this causes the parser cache to be fragmented by user language!
668 * To avoid cache fragmentation, output should not depend on the user language.
669 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
671 * @note This function will trigger a cache fragmentation by recording the
672 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
673 * when the page is rendered based on the language of the user.
675 * @note When saving, this will return the default language instead of the user's.
676 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
681 public function getUserLangObj() {
682 return $this->getOption( 'userlang' );
686 * Same as getUserLangObj() but returns a string instead.
688 * @warning: Calling this causes the parser cache to be fragmented by user language!
689 * To avoid cache fragmentation, output should not depend on the user language.
690 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
692 * @see getUserLangObj()
694 * @return string Language code
697 public function getUserLang() {
698 return $this->getUserLangObj()->getCode();
702 * Set the user language used by the parser for this page and split the parser cache.
703 * @param string|Language $x New value
704 * @return Language Old value
706 public function setUserLang( $x ) {
707 if ( is_string( $x ) ) {
708 $x = Language
::factory( $x );
711 return $this->setOptionLegacy( 'userlang', $x );
715 * Are magic ISBN links enabled?
719 public function getMagicISBNLinks() {
720 return $this->getOption( 'magicISBNLinks' );
724 * Are magic PMID links enabled?
728 public function getMagicPMIDLinks() {
729 return $this->getOption( 'magicPMIDLinks' );
732 * Are magic RFC links enabled?
736 public function getMagicRFCLinks() {
737 return $this->getOption( 'magicRFCLinks' );
741 * If the wiki is configured to allow raw html ($wgRawHtml = true)
742 * is it allowed in the specific case of parsing this page.
744 * This is meant to disable unsafe parser tags in cases where
745 * a malicious user may control the input to the parser.
747 * @note This is expected to be true for normal pages even if the
748 * wiki has $wgRawHtml disabled in general. The setting only
749 * signifies that raw html would be unsafe in the current context
750 * provided that raw html is allowed at all.
754 public function getAllowUnsafeRawHtml() {
755 return $this->getOption( 'allowUnsafeRawHtml' );
759 * If the wiki is configured to allow raw html ($wgRawHtml = true)
760 * is it allowed in the specific case of parsing this page.
761 * @see self::getAllowUnsafeRawHtml()
763 * @param bool|null $x Value to set or null to get current value
764 * @return bool Current value for allowUnsafeRawHtml
766 public function setAllowUnsafeRawHtml( $x ) {
767 return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
771 * Class to use to wrap output from Parser::parse()
773 * @return string|bool
775 public function getWrapOutputClass() {
776 return $this->getOption( 'wrapclass' );
780 * CSS class to use to wrap output from Parser::parse()
782 * @param string $className Class name to use for wrapping.
783 * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31.
784 * @return string|bool Current value
786 public function setWrapOutputClass( $className ) {
787 if ( $className === true ) { // DWIM, they probably want the default class name
788 $className = 'mw-parser-output';
790 if ( $className === false ) {
791 wfDeprecated( __METHOD__
. '( false )', '1.31' );
793 return $this->setOption( 'wrapclass', $className );
797 * Callback for current revision fetching; first argument to call_user_func().
801 public function getCurrentRevisionCallback() {
802 return $this->getOption( 'currentRevisionCallback' );
806 * Callback for current revision fetching; first argument to call_user_func().
808 * @param callable|null $x New value (null is no change)
809 * @return callable Old value
811 public function setCurrentRevisionCallback( $x ) {
812 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
816 * Callback for template fetching; first argument to call_user_func().
819 public function getTemplateCallback() {
820 return $this->getOption( 'templateCallback' );
824 * Callback for template fetching; first argument to call_user_func().
825 * @param callable|null $x New value (null is no change)
826 * @return callable Old value
828 public function setTemplateCallback( $x ) {
829 return $this->setOptionLegacy( 'templateCallback', $x );
833 * Callback to generate a guess for {{REVISIONID}}
835 * @return callable|null
837 public function getSpeculativeRevIdCallback() {
838 return $this->getOption( 'speculativeRevIdCallback' );
842 * Callback to generate a guess for {{REVISIONID}}
844 * @param callable|null $x New value (null is no change)
845 * @return callable|null Old value
847 public function setSpeculativeRevIdCallback( $x ) {
848 return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
854 * Timestamp used for {{CURRENTDAY}} etc.
857 public function getTimestamp() {
858 if ( !isset( $this->mTimestamp
) ) {
859 $this->mTimestamp
= wfTimestampNow();
861 return $this->mTimestamp
;
865 * Timestamp used for {{CURRENTDAY}} etc.
866 * @param string|null $x New value (null is no change)
867 * @return string Old value
869 public function setTimestamp( $x ) {
870 return wfSetVar( $this->mTimestamp
, $x );
874 * Create "edit section" links?
875 * @deprecated since 1.31, use ParserOutput::getText() options instead.
878 public function getEditSection() {
879 return $this->mEditSection
;
883 * Create "edit section" links?
884 * @deprecated since 1.31, use ParserOutput::getText() options instead.
885 * @param bool|null $x New value (null is no change)
886 * @return bool Old value
888 public function setEditSection( $x ) {
889 return wfSetVar( $this->mEditSection
, $x );
893 * Set the redirect target.
895 * Note that setting or changing this does not *make* the page a redirect
896 * or change its target, it merely records the information for reference
900 * @param Title|null $title
902 function setRedirectTarget( $title ) {
903 $this->redirectTarget
= $title;
907 * Get the previously-set redirect target.
912 function getRedirectTarget() {
913 return $this->redirectTarget
;
917 * Extra key that should be present in the parser cache key.
918 * @warning Consider registering your additional options with the
919 * ParserOptionsRegister hook instead of using this method.
922 public function addExtraKey( $key ) {
923 $this->mExtraKey
.= '!' . $key;
930 public function getUser() {
935 * @warning For interaction with the parser cache, use
936 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
937 * ParserOptions::newCanonical() instead.
939 * @param Language $lang
941 public function __construct( $user = null, $lang = null ) {
942 if ( $user === null ) {
944 if ( $wgUser === null ) {
950 if ( $lang === null ) {
952 if ( !StubObject
::isRealObject( $wgLang ) ) {
957 $this->initialiseFromUser( $user, $lang );
961 * Get a ParserOptions object for an anonymous user
962 * @warning For interaction with the parser cache, use
963 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
964 * ParserOptions::newCanonical() instead.
966 * @return ParserOptions
968 public static function newFromAnon() {
970 return new ParserOptions( new User
, $wgContLang );
974 * Get a ParserOptions object from a given user.
975 * Language will be taken from $wgLang.
977 * @warning For interaction with the parser cache, use
978 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
979 * ParserOptions::newCanonical() instead.
981 * @return ParserOptions
983 public static function newFromUser( $user ) {
984 return new ParserOptions( $user );
988 * Get a ParserOptions object from a given user and language
990 * @warning For interaction with the parser cache, use
991 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
992 * ParserOptions::newCanonical() instead.
994 * @param Language $lang
995 * @return ParserOptions
997 public static function newFromUserAndLang( User
$user, Language
$lang ) {
998 return new ParserOptions( $user, $lang );
1002 * Get a ParserOptions object from a IContextSource object
1004 * @warning For interaction with the parser cache, use
1005 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
1006 * ParserOptions::newCanonical() instead.
1007 * @param IContextSource $context
1008 * @return ParserOptions
1010 public static function newFromContext( IContextSource
$context ) {
1011 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1015 * Creates a "canonical" ParserOptions object
1017 * For historical reasons, certain options have default values that are
1018 * different from the canonical values used for caching.
1021 * @param User|null $user
1022 * @param Language|StubObject|null $lang
1023 * @return ParserOptions
1025 public static function newCanonical( User
$user = null, $lang = null ) {
1026 $ret = new ParserOptions( $user, $lang );
1027 foreach ( self
::getCanonicalOverrides() as $k => $v ) {
1028 $ret->setOption( $k, $v );
1034 * Get default option values
1035 * @warning If you change the default for an existing option (unless it's
1036 * being overridden by self::getCanonicalOverrides()), all existing parser
1037 * cache entries will be invalid. To avoid bugs, you'll need to handle
1038 * that somehow (e.g. with the RejectParserCacheValue hook) because
1039 * MediaWiki won't do it for you.
1042 private static function getDefaults() {
1043 global $wgInterwikiMagic, $wgAllowExternalImages,
1044 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1045 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1046 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1047 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1048 $wgEnableMagicLinks, $wgContLang;
1050 if ( self
::$defaults === null ) {
1051 // *UPDATE* ParserOptions::matches() if any of this changes as needed
1053 'dateformat' => null,
1055 'interfaceMessage' => false,
1056 'targetLanguage' => null,
1057 'removeComments' => true,
1058 'enableLimitReport' => false,
1059 'preSaveTransform' => true,
1060 'isPreview' => false,
1061 'isSectionPreview' => false,
1062 'printable' => false,
1063 'allowUnsafeRawHtml' => true,
1064 'wrapclass' => 'mw-parser-output',
1065 'currentRevisionCallback' => [ Parser
::class, 'statelessFetchRevision' ],
1066 'templateCallback' => [ Parser
::class, 'statelessFetchTemplate' ],
1067 'speculativeRevIdCallback' => null,
1070 Hooks
::run( 'ParserOptionsRegister', [
1073 &self
::$lazyOptions,
1076 ksort( self
::$inCacheKey );
1079 // Unit tests depend on being able to modify the globals at will
1080 return self
::$defaults +
[
1081 'interwikiMagic' => $wgInterwikiMagic,
1082 'allowExternalImages' => $wgAllowExternalImages,
1083 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1084 'enableImageWhitelist' => $wgEnableImageWhitelist,
1085 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1086 'maxIncludeSize' => $wgMaxArticleSize * 1024,
1087 'maxPPNodeCount' => $wgMaxPPNodeCount,
1088 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1089 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1090 'maxTemplateDepth' => $wgMaxTemplateDepth,
1091 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1092 'externalLinkTarget' => $wgExternalLinkTarget,
1093 'cleanSignatures' => $wgCleanSignatures,
1094 'disableContentConversion' => $wgDisableLangConversion,
1095 'disableTitleConversion' => $wgDisableLangConversion ||
$wgDisableTitleConversion,
1096 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1097 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1098 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1099 'numberheadings' => User
::getDefaultOption( 'numberheadings' ),
1100 'thumbsize' => User
::getDefaultOption( 'thumbsize' ),
1101 'stubthreshold' => 0,
1102 'userlang' => $wgContLang,
1107 * Get "canonical" non-default option values
1108 * @see self::newCanonical
1109 * @warning If you change the override for an existing option, all existing
1110 * parser cache entries will be invalid. To avoid bugs, you'll need to
1111 * handle that somehow (e.g. with the RejectParserCacheValue hook) because
1112 * MediaWiki won't do it for you.
1115 private static function getCanonicalOverrides() {
1116 global $wgEnableParserLimitReporting;
1120 'enableLimitReport' => $wgEnableParserLimitReporting,
1128 * @param Language $lang
1130 private function initialiseFromUser( $user, $lang ) {
1131 $this->options
= self
::getDefaults();
1133 $this->mUser
= $user;
1134 $this->options
['numberheadings'] = $user->getOption( 'numberheadings' );
1135 $this->options
['thumbsize'] = $user->getOption( 'thumbsize' );
1136 $this->options
['stubthreshold'] = $user->getStubThreshold();
1137 $this->options
['userlang'] = $lang;
1141 * Check if these options match that of another options set
1143 * This ignores report limit settings that only affect HTML comments
1145 * @param ParserOptions $other
1149 public function matches( ParserOptions
$other ) {
1150 // Populate lazy options
1151 foreach ( self
::$lazyOptions as $name => $callback ) {
1152 if ( $this->options
[$name] === null ) {
1153 $this->options
[$name] = call_user_func( $callback, $this, $name );
1155 if ( $other->options
[$name] === null ) {
1156 $other->options
[$name] = call_user_func( $callback, $other, $name );
1160 // Compare most options
1161 $options = array_keys( $this->options
);
1162 $options = array_diff( $options, [
1163 'enableLimitReport', // only affects HTML comments
1165 foreach ( $options as $option ) {
1166 $o1 = $this->optionToString( $this->options
[$option] );
1167 $o2 = $this->optionToString( $other->options
[$option] );
1168 if ( $o1 !== $o2 ) {
1173 // Compare most other fields
1174 $fields = array_keys( get_class_vars( __CLASS__
) );
1175 $fields = array_diff( $fields, [
1176 'defaults', // static
1177 'lazyOptions', // static
1178 'inCacheKey', // static
1179 'options', // Already checked above
1180 'onAccessCallback', // only used for ParserOutput option tracking
1182 foreach ( $fields as $field ) {
1183 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1192 * Registers a callback for tracking which ParserOptions which are used.
1193 * This is a private API with the parser.
1194 * @param callable $callback
1196 public function registerWatcher( $callback ) {
1197 $this->onAccessCallback
= $callback;
1201 * Called when an option is accessed.
1202 * Calls the watcher that was set using registerWatcher().
1203 * Typically, the watcher callback is ParserOutput::registerOption().
1204 * The information registered that way will be used by ParserCache::save().
1206 * @param string $optionName Name of the option
1208 public function optionUsed( $optionName ) {
1209 if ( $this->onAccessCallback
) {
1210 call_user_func( $this->onAccessCallback
, $optionName );
1215 * Returns the full array of options that would have been used by
1217 * Used to get the old parser cache entries when available.
1218 * @deprecated since 1.30. You probably want self::allCacheVaryingOptions() instead.
1221 public static function legacyOptions() {
1222 wfDeprecated( __METHOD__
, '1.30' );
1234 * Return all option keys that vary the options hash
1238 public static function allCacheVaryingOptions() {
1239 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1240 // already been called.
1241 if ( self
::$defaults === null ) {
1242 self
::getDefaults();
1244 return array_keys( array_filter( self
::$inCacheKey ) );
1248 * Convert an option to a string value
1249 * @param mixed $value
1252 private function optionToString( $value ) {
1253 if ( $value === true ) {
1255 } elseif ( $value === false ) {
1257 } elseif ( $value === null ) {
1259 } elseif ( $value instanceof Language
) {
1260 return $value->getCode();
1261 } elseif ( is_array( $value ) ) {
1262 return '[' . join( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1264 return (string)$value;
1269 * Generate a hash string with the values set on these ParserOptions
1270 * for the keys given in the array.
1271 * This will be used as part of the hash key for the parser cache,
1272 * so users sharing the options with vary for the same page share
1273 * the same cached data safely.
1276 * @param array $forOptions
1277 * @param Title $title Used to get the content language of the page (since r97636)
1278 * @return string Page rendering hash
1280 public function optionsHash( $forOptions, $title = null ) {
1281 global $wgRenderHashAppend;
1283 $options = $this->options
;
1284 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1285 $inCacheKey = self
::$inCacheKey;
1287 // Historical hack: 'editsection' hasn't been a true parser option since
1288 // Feb 2015 (instead the parser outputs a constant placeholder and post-parse
1289 // processing handles the option). But Wikibase forces it in $forOptions
1290 // and expects the cache key to still vary on it for T85252.
1291 // @deprecated since 1.30, Wikibase should use addExtraKey() or something instead.
1292 if ( in_array( 'editsection', $forOptions, true ) ) {
1293 $options['editsection'] = $this->mEditSection
;
1294 $defaults['editsection'] = true;
1295 $inCacheKey['editsection'] = true;
1296 ksort( $inCacheKey );
1299 // We only include used options with non-canonical values in the key
1300 // so adding a new option doesn't invalidate the entire parser cache.
1301 // The drawback to this is that changing the default value of an option
1302 // requires manual invalidation of existing cache entries, as mentioned
1303 // in the docs on the relevant methods and hooks.
1305 foreach ( $inCacheKey as $option => $include ) {
1306 if ( $include && in_array( $option, $forOptions, true ) ) {
1307 $v = $this->optionToString( $options[$option] );
1308 $d = $this->optionToString( $defaults[$option] );
1310 $values[] = "$option=$v";
1315 $confstr = $values ?
join( '!', $values ) : 'canonical';
1317 // add in language specific options, if any
1318 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1319 if ( !is_null( $title ) ) {
1320 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1323 $confstr .= $wgContLang->getExtraHashOptions();
1326 $confstr .= $wgRenderHashAppend;
1328 if ( $this->mExtraKey
!= '' ) {
1329 $confstr .= $this->mExtraKey
;
1332 // Give a chance for extensions to modify the hash, if they have
1333 // extra options or other effects on the parser cache.
1334 Hooks
::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1336 // Make it a valid memcached key fragment
1337 $confstr = str_replace( ' ', '_', $confstr );
1343 * Test whether these options are safe to cache
1347 public function isSafeToCache() {
1348 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1349 foreach ( $this->options
as $option => $value ) {
1350 if ( empty( self
::$inCacheKey[$option] ) ) {
1351 $v = $this->optionToString( $value );
1352 $d = $this->optionToString( $defaults[$option] );
1362 * Sets a hook to force that a page exists, and sets a current revision callback to return
1363 * a revision with custom content when the current revision of the page is requested.
1366 * @param Title $title
1367 * @param Content $content
1368 * @param User $user The user that the fake revision is attributed to
1369 * @return ScopedCallback to unset the hook
1371 public function setupFakeRevision( $title, $content, $user ) {
1372 $oldCallback = $this->setCurrentRevisionCallback(
1374 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1376 if ( $titleToCheck->equals( $title ) ) {
1377 return new Revision( [
1378 'page' => $title->getArticleID(),
1379 'user_text' => $user->getName(),
1380 'user' => $user->getId(),
1381 'parent_id' => $title->getLatestRevID(),
1383 'content' => $content
1386 return call_user_func( $oldCallback, $titleToCheck, $parser );
1392 $wgHooks['TitleExists'][] =
1393 function ( $titleToCheck, &$exists ) use ( $title ) {
1394 if ( $titleToCheck->equals( $title ) ) {
1398 end( $wgHooks['TitleExists'] );
1399 $key = key( $wgHooks['TitleExists'] );
1400 LinkCache
::singleton()->clearBadLink( $title->getPrefixedDBkey() );
1401 return new ScopedCallback( function () use ( $title, $key ) {
1403 unset( $wgHooks['TitleExists'][$key] );
1404 LinkCache
::singleton()->clearLink( $title );
1410 * For really cool vim folding this needs to be at the end:
1411 * vim: foldmarker=@{,@} foldmethod=marker